use crate::{FromPacket, MutablePacket, Packet, PacketSize};
use crate::rtcp::RtcpType;
use alloc::vec::Vec;
use pnet_macros::packet;
use pnet_macros_support::{types::{u1, u16be, u2, u32be, u5, u3}, packet::PrimitiveValues};

#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum RtcpXrBlock {
    StatisticsSummary(StatisticsSummaryBlock),
	VoipMetrics(VoipMetricsBlock),

	KnownType(CommonBlock),
}

#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub enum RtcpXrBlockPacket<'a> {
    StatisticsSummary(StatisticsSummaryBlockPacket<'a>),
	VoipMetrics(VoipMetricsBlockPacket<'a>),

	KnownType(CommonBlockPacket<'a>),
}

impl RtcpXrBlockPacket<'_> {
	#[must_use]
	pub fn new(pkt: &[u8]) -> Option<RtcpXrBlockPacket<'_>> {
		RtcpXrBlockType::from_packet(pkt).and_then(|rtcp_xr_bt| rtcp_xr_bt.decode(pkt))
	}
}

impl<'a> Packet for RtcpXrBlockPacket<'a> {
	fn packet(&self) -> &[u8] {
		match self {
			Self::StatisticsSummary(s) => s.packet(),
			Self::VoipMetrics(s) => s.packet(),
			Self::KnownType(s) => s.packet(),
		}
	}

	fn payload(&self) -> &[u8] {
		match self {
			Self::StatisticsSummary(s) => s.payload(),
			Self::VoipMetrics(s) => s.payload(),
			Self::KnownType(s) => s.payload(),
		}
	}
}

impl<'a> FromPacket for RtcpXrBlockPacket<'a> {
	type T = RtcpXrBlock;

	fn from_packet(&self) -> Self::T {
		match self {
			Self::StatisticsSummary(s) => RtcpXrBlock::StatisticsSummary(s.from_packet()),
			Self::VoipMetrics(s) => RtcpXrBlock::VoipMetrics(s.from_packet()),
			Self::KnownType(s) => RtcpXrBlock::KnownType(s.from_packet()),
		}
	}
}

impl<'a> PacketSize for RtcpXrBlockPacket<'a> {
	fn packet_size(&self) -> usize {
		match self {
			Self::StatisticsSummary(s) => s.packet_size(),
			Self::VoipMetrics(s) => s.packet_size(),
			Self::KnownType(s) => s.packet_size(),
		}
	}
}

#[derive(Debug, PartialEq)]
#[non_exhaustive]
pub enum MutableRtcpXrBlockPacket<'a> {
    StatisticsSummary(MutableStatisticsSummaryBlockPacket<'a>),
	VoipMetrics(MutableVoipMetricsBlockPacket<'a>),

	KnownType(MutableCommonBlockPacket<'a>),
}

impl MutableRtcpXrBlockPacket<'_> {
	#[must_use]
	pub fn new(pkt: &mut [u8]) -> Option<MutableRtcpXrBlockPacket<'_>> {
		RtcpXrBlockType::from_packet(pkt).and_then(|rtcp_xr_bt| rtcp_xr_bt.decode_mut(pkt))
	}
}

impl<'a> Packet for MutableRtcpXrBlockPacket<'a> {
	fn packet(&self) -> &[u8] {
		match self {
			Self::StatisticsSummary(s) => s.packet(),
			Self::VoipMetrics(s) => s.packet(),
			Self::KnownType(s) => s.packet(),
		}
	}

	fn payload(&self) -> &[u8] {
		match self {
			Self::StatisticsSummary(s) => s.payload(),
			Self::VoipMetrics(s) => s.payload(),
			Self::KnownType(s) => s.payload(),
		}
	}
}

impl<'a> MutablePacket for MutableRtcpXrBlockPacket<'a> {
	fn packet_mut(&mut self) -> &mut [u8] {
		match self {
			Self::StatisticsSummary(s) => s.packet_mut(),
			Self::VoipMetrics(s) => s.packet_mut(),
			Self::KnownType(s) => s.packet_mut(),
		}
	}

	fn payload_mut(&mut self) -> &mut [u8] {
		match self {
			Self::StatisticsSummary(s) => s.payload_mut(),
			Self::VoipMetrics(s) => s.payload_mut(),
			Self::KnownType(s) => s.payload_mut(),
		}
	}
}
impl<'a> FromPacket for MutableRtcpXrBlockPacket<'a> {
	type T = RtcpXrBlock;

	fn from_packet(&self) -> Self::T {
		match self {
			Self::StatisticsSummary(s) => RtcpXrBlock::StatisticsSummary(s.from_packet()),
			Self::VoipMetrics(s) => RtcpXrBlock::VoipMetrics(s.from_packet()),
			Self::KnownType(s) => RtcpXrBlock::KnownType(s.from_packet()),
		}
	}
}

impl<'a> PacketSize for MutableRtcpXrBlockPacket<'a> {
	fn packet_size(&self) -> usize {
		match self {
			Self::StatisticsSummary(s) => s.packet_size(),
			Self::VoipMetrics(s) => s.packet_size(),
			Self::KnownType(s) => s.packet_size(),
		}
	}
}

#[packet]
#[derive(Eq, PartialEq)]
pub struct ExtendedReport {
	pub version: u2,

	pub padding: u1,

	pub reserved: u5,

	#[construct_with(u8)]
	pub packet_type: RtcpType,

	pub pkt_length: u16be,

	pub ssrc: u32be,

    #[length = "(pkt_length + 1 - 2) * 4"]
	#[payload]
	pub payload: Vec<u8>,
}

impl ExtendedReport {
    pub fn parse_xr_blocks(&self) -> Vec<RtcpXrBlock> {
        let mut p: &[u8] = &self.payload;
        let mut blocks = Vec::new();
        while p.len() > 0 {
            if let Some(block) = RtcpXrBlockPacket::new(p) {
                blocks.push(block.from_packet());
                p = &p[block.packet_size()..];
            } else {
                break;
            }
        }
        blocks
    }
}

#[packet]
#[derive(Eq, PartialEq)]
pub struct CommonBlock {
    #[construct_with(u8)]
	pub block_type: RtcpXrBlockType,

	pub reserved: u8,

	pub block_length: u16be,

	#[length = "block_length * 4"]
	#[payload]
	pub payload: Vec<u8>,
}

#[packet]
#[derive(Eq, PartialEq)]
pub struct RrtrBlock {
    #[construct_with(u8)]
	pub block_type: RtcpXrBlockType,

	pub reserved: u8,

	pub block_length: u16be,

	pub ntp_timestamp_second: u32be,

	pub ntp_timestamp_fraction: u32be,

	#[payload]
	pub payload: Vec<u8>,
}

#[packet]
#[derive(Eq, PartialEq)]
pub struct StatisticsSummaryBlock {
    #[construct_with(u8)]
	pub block_type: RtcpXrBlockType,
	pub loss_report_flag: u1,
	pub duplicate_report_flag: u1,
    pub jitter_flag: u1,
    pub toh_flag: u2,
    pub reserved: u3,
    pub block_length: u16be,

    pub ssrc: u32be,

	pub begin_seq: u16be,
    pub end_seq: u16be,

    pub lost_packets: u32be,

    pub dup_packets: u32be,
    
    pub min_jitter: u32be,

    pub max_jitter: u32be,

    pub mean_jitter: u32be,

    pub dev_jitter: u32be,

    pub min_ttl_or_hl: u8,
    pub max_ttl_or_hl: u8,
    pub mean_ttl_or_hl: u8,
    pub dev_ttl_or_hl: u8,

	#[payload]
	pub payload: Vec<u8>,
}

#[packet]
#[derive(Eq, PartialEq)]
pub struct VoipMetricsBlock {
    #[construct_with(u8)]
	pub block_type: RtcpXrBlockType,
	pub reserved: u8,
	pub block_length: u16be,

    pub ssrc: u32be,

	pub loss_rate: u8,
    pub discard_rate: u8,
	pub burst_density: u8,
    pub gap_density: u8,

	pub burst_duration: u16be,
    pub gap_duration: u16be,

    pub round_trip_delay: u16be,
    pub end_system_delay: u16be,

    pub signal_level: u8,
    pub noise_level: u8,
    pub rerl: u8,
    pub g_min: u8,

    pub r_factor: u8,
    pub ext_r_factor: u8,
    pub mos_lq: u8,
    pub mos_cq: u8,

    pub rx_config: u8,
    pub reserved1: u8,
    pub jb_nominal: u16be,

    pub jb_maximum: u16be,
    pub jb_abs_max: u16be,

	#[payload]
	pub payload: Vec<u8>,
}

#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
#[non_exhaustive]
pub enum RtcpXrBlockType {
    /// 1 Loss RLE Report Block
	LossRLE,
    /// 2 Duplicate RLE Report Block
    DuplicateRLE,
    /// 3 Packet Receipt Times Report Block
    Prtr,
    /// 4 Receiver Reference Time Report Block
    Rrtr,
    /// 5 DLRR Report Block
    Dlrr,
    /// 6 Statistics Summary Report Block
    StatisticsSummary,
    /// 7 VoIP Metrics Report Block
    VoipMetrics,

	/// Unknown message type.
	Unassigned(u8),
}

impl<'a> RtcpXrBlockType {
	#[must_use]
	pub fn new(val: u8) -> Self {
		match val {
			1 => Self::LossRLE,
			2 => Self::DuplicateRLE,
			3 => Self::Prtr,
			4 => Self::Rrtr,
			5 => Self::Dlrr,
			6 => Self::StatisticsSummary,
			7 => Self::VoipMetrics,
			_ => Self::Unassigned(val),
		}
	}

	#[must_use]
	pub fn decode(&self, pkt: &'a [u8]) -> Option<RtcpXrBlockPacket<'a>> {
		match self {
			Self::StatisticsSummary => StatisticsSummaryBlockPacket::new(pkt).map(RtcpXrBlockPacket::StatisticsSummary),
			Self::VoipMetrics => VoipMetricsBlockPacket::new(pkt).map(RtcpXrBlockPacket::VoipMetrics),
			_ => CommonBlockPacket::new(pkt).map(RtcpXrBlockPacket::KnownType),
		}
	}

	pub fn decode_mut(&self, pkt: &'a mut [u8]) -> Option<MutableRtcpXrBlockPacket<'a>> {
		match self {
			Self::StatisticsSummary =>
                MutableStatisticsSummaryBlockPacket::new(pkt).map(MutableRtcpXrBlockPacket::StatisticsSummary),
			Self::VoipMetrics =>
                MutableVoipMetricsBlockPacket::new(pkt).map(MutableRtcpXrBlockPacket::VoipMetrics),
			_ =>
                MutableCommonBlockPacket::new(pkt).map(MutableRtcpXrBlockPacket::KnownType),
		}
	}

	pub fn from_packet(pkt: &[u8]) -> Option<Self> {
		pkt.get(0).copied().map(Self::new)
	}
}

impl PrimitiveValues for RtcpXrBlockType {
	type T = (u8,);

	fn to_primitive_values(&self) -> Self::T {
		match self {
			Self::LossRLE => (1,),
			Self::DuplicateRLE => (2,),
			Self::Prtr => (3,),
			Self::Rrtr => (4,),
			Self::Dlrr => (5,),
			Self::StatisticsSummary => (6,),
			Self::VoipMetrics => (7,),

			Self::Unassigned(val) => (*val,),
		}
	}
}
